//pm.c
//-this file contains the Process Manager's necessary components
//David Dumas, Hakan Akkan
//CSE 325
//Process Manager

#include <time.h>
#include <stdlib.h>
#include <string.h>

#include <pm.h>
#include <sched.h>

void enqueue(struct process *p, struct queue_definition *q);
static int next_process_id = 0;
int last_slot;

/* For initializing queues lists finding out num_procs etc? */
void init_pm(){
	next_process_id = 0;
	last_slot = -1;
	init_process_table();
	new_queue.n_elements = 0;
	new_queue.head = new_queue.tail = NULL;
	ready_queue.n_elements = 0;
	ready_queue.head = ready_queue.tail = NULL;
	waiting_queue.n_elements = 0;
	waiting_queue.head = waiting_queue.tail = NULL;
	terminated_queue.n_elements = 0;
	terminated_queue.head = terminated_queue.tail = NULL;
	
}

int get_pm_size(){
	int out = 0;
	out += sizeof(struct process) * MAX_PROCS; // Size of process table
	out += sizeof(struct process*) * N_PROC_BUCKETS; // Hash table itself
	out += sizeof(struct queue_definition) * 4; // Process queues
	out += sizeof(struct process*) * NUM_PROC; // Running list
	return out;
}

//gets a unique identifier for new processes
// Lets try starting from 1
int get_unique_id()
{
/*
	int rand_val;
	srand(time(NULL));
	
	rand_val = rand() % 100000 + 1;

	return rand_val;
*/
  int pid;
  do {
    pid = next_process_id++; 
  } while (get_process_by_pid(pid));
  
	return pid;
}

// Returns a pointer to the queue_definition that is currently containing 
// the input process
struct queue_definition *process_to_queue(struct process *p){
	switch(p->state){
		case P_NEW: return &new_queue; break;
		case P_READY: return &ready_queue; break;
		case P_WAIT: return &waiting_queue; break;
		case P_TERMINATED: return &terminated_queue; break;
		case P_RUNNING: 
		default: return NULL; break;
	}
}

/* 
 * find_empty_process iterates thru the procs[MAX_PROCS] until it finds
 * an empty one and returns it
 */
struct process* find_empty_process(){
	// Start from the next_slot until we find an empty one.
	// If we used up all slots, user needs to terminate one before creating
	// a new process.
	int next = last_slot;
	
	while((next = ((++next) % MAX_PROCS)) != last_slot){
		if(procs[next].valid == 0){
			last_slot = next;
			return &procs[next];
		}
	}	
	
	// We checked all slot but could not find an empty one.
	return NULL;
}

/*
	TODO: How to find out who made the call to this function? We need this
	for setting the parent for it!
*/
//function that creates new process from inputs
struct process* new_process(int group_no, char* cmd, int pid, int n_pages)
{
	//struct process *new_p = (struct process*)kalloc(sizeof(struct process));
	struct process *new_p;

	
	if( (new_p = find_empty_process()) == NULL){
		return NULL;
	}

	init_sched_info(&new_p->sched);
	new_p->sched.group_no = group_no;
	new_p->pid = pid == -1 ? get_unique_id() : pid;
	if(cmd != NULL) strcpy(new_p->cmd,cmd);
		
	//we start in new
	new_p->state = P_NEW;
	new_p->valid = 1;
	new_p->processor_number = -1; // not running yet

	new_p->stats.time_allowed = 0;
	new_p->stats.time_run = 0;
	new_p->stats.wait_since_run = 0;
	new_p->stats.wait_total = 0;
	
	alloc_pt(&(new_p->pt), new_p->pid, n_pages);
	
	/* XXX if kalloc that we implement does not zero out the memory allocated,
	   we need to set some pointers to NULL here because some functionality
	   depends on NULL pointers (next_p prev_p etc.)
	 */
	
	new_p->parent = NULL;
	new_p->child = NULL;
	new_p->sibling = NULL;
	new_p->next_p = NULL;
	new_p->prev_p = NULL;
	new_p->next_hash_p = NULL;
	
	/*more to come*/
	
	enqueue(new_p,&new_queue);
	// put the process into process table.
	switch(put_process(new_p)){
		case -1: // already exists
		case -2: // null new_p?
		case 0:
		default: break;
	}
	
	return new_p;
}


void change_group(struct process* p, int gr_no){
	if(p->state == P_RUNNING || p->state == P_READY){
		remove_proc_from_ready_group(p);
	}
	p->sched.group_no = gr_no;
	if(p->state == P_RUNNING || p->state == P_READY){
		add_ready_proc_to_group(p);
	}
}



//general queue manipulation functions
////////////////////

//enqueue a process into a queue
void enqueue(struct process *p, struct queue_definition *q)
{
	if(q->head == NULL){
		q->head = q->tail = p;
		p->prev_p = p->next_p = NULL;
	} else {
		//the tail of the queue has it's "next" set to p
		p->prev_p = q->tail;
		q->tail->next_p = p;
		q->tail = p;
	}
	//record queue size change
	q->n_elements += 1;
}

struct process* peak(struct queue_definition *q)
{
	return q->head;
}

//dequeue the head process from a queue
struct process* dequeue(struct queue_definition *q)
{
	struct process *old_p;
	
	//get the head of the queue
	old_p = q->head;
	
	if(old_p == NULL) return NULL;
	
	//change the head to the "next" in the queue
	q->head = old_p->next_p;
			
	if(q->head == NULL){
		q->tail = NULL;
	} else {
		q->head->prev_p = NULL;
	}
	
	//record the queue size change
	q->n_elements -= 1;
	
	return old_p;
}

//dequeue the given process from its queue
int dequeue_p(struct process *p, struct queue_definition *q)
{
	struct process *old_p = q->head;

	if(q->head == NULL) return -1; // nothing in the queue
	
	//find the process we want to "dequeue"
	while(old_p->pid != p->pid && old_p->next_p != NULL)
	{
		old_p = old_p->next_p;
	}
	
	//if we've found the process, set pointers of neighbors to reflect the removal
	if(old_p->pid == p->pid){
		if(old_p->next_p == NULL){
			// it was the tail
			if((q->tail = old_p->prev_p)) q->tail->next_p = NULL;
		} else {
			old_p->next_p->prev_p = old_p->prev_p;
		}
		if(old_p->prev_p == NULL){
			// it was the head
			if((q->head = old_p->next_p)) q->head->prev_p = NULL;
		} else {
			old_p->prev_p->next_p = old_p->next_p;
		}
		
		old_p->prev_p = old_p->next_p = NULL;
		//record the queue size change
		q->n_elements -= 1;
		return 0;
	} else {
		return -1;
	}
}

inline int _dequeue_p(struct process *p){
	return dequeue_p(p,process_to_queue(p));
}


//a general purpose from_queue -> to_queue operation on a process for use in all transitions
void move_process(struct queue_definition *from_queue,struct queue_definition *to_queue, struct process *p)
{
	dequeue_p(p, from_queue);
	enqueue(p, to_queue);
}


// Returns the processes running on the input processor
inline struct process* get_process_from_processor(int processor){
	return processor < NUM_PROC ? running_list[processor] : NULL;
}

//these are all state transition funcitons, assuming they have view of global PM variables,
//these are the only functions we have to call to make transitions
///////////////////////
//I think these functions will become more useful as we add more distinctive properties, or changes
//that must take place during each state transition.  Right now they're all the same

int queue_to_terminated(struct process *p){
	int result = 0;
	switch(p->state){
		case P_READY:
			remove_proc_from_ready_group(p);
		case P_NEW:
		case P_WAIT:
			result = _dequeue_p(p);
			break;
		case P_RUNNING:
			// Remove the process out of its processor
			running_list[p->processor_number] = NULL;
			remove_proc_from_ready_group(p);
			break;
		default:
			result = -2;
			break;
	}

	if(result != 0) return result;
	
	p->processor_number = -1;
	enqueue(p,&terminated_queue);
	p->state = P_TERMINATED;
	
	dealloc_pt(p);
	
	return 0;
}

int queue_to_ready(struct process *p){
	int result = 0;
	switch(p->state){
		case P_NEW:
		case P_WAIT:
			result = _dequeue_p(p);
			// A non-ready process became ready. Add it to its group
			if(result == 0) add_ready_proc_to_group(p);
			break;
		case P_RUNNING:
			running_list[p->processor_number] = NULL;
			break;
		default:
			result = -2;
			break;
	}
	if(result != 0) return result;

	p->processor_number = -1;
	enqueue(p,&ready_queue);
	p->state = P_READY;	
	
	return 0;
}

int queue_to_running(struct process *p){
	int result = 0;
	int processor = 0;
	switch(p->state){
		case P_READY:
			result = _dequeue_p(p);
			break;
		default:
			result = -2;
			break;
	}
	if(result != 0) return result;

	while(processor < NUM_PROC && running_list[processor] != NULL) processor++;
	
	//this should never be true when queue_to_running is called from "GO"
	if(processor == NUM_PROC){
		// All are occupied.
		srand(time(NULL));
		processor = rand() % NUM_PROC;
		// free the processor
		queue_to_ready(running_list[processor]);
	}
	// running_list[processor] is now empty
	running_list[processor] = p;
	p->processor_number = processor;
	p->state = P_RUNNING;
		
	return 0;
}

int queue_to_waiting(struct process *p){
	int result = 0;
	switch(p->state){
		case P_RUNNING:
			running_list[p->processor_number] = NULL;
			remove_proc_from_ready_group(p);
			break;
		default:
			result = -2;
			break;
	}
	if(result != 0) return result;

	p->processor_number = -1;
	enqueue(p,&waiting_queue);
	p->state = P_WAIT;	
	
	return 0;
}

//function that removes a process from the termination queue, releasing its resources
int terminate_process(struct process* p)
{
	if(p->state != P_TERMINATED) return -1;
	int result = _dequeue_p(p);
	if(result != 0) return result;
	if(remove_process(p->pid) == NULL) return -2;
	//do resource freeing
	p->valid = 0; // make the memory allocated for this PCB reusable.
	return result;
}

